/*
 *  ifxsql.cpp - Informix dbExpress interfaces implementation
 *
 *    Copyright (c) 2001-2006, Luxena Company. All rights reserved.
 *
 *  Purpose:
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>

#include <sqlhdr.h>
#include <sqliapi.h>
#ifdef WIN32
#include <login.h>
#endif

#include "trace.hpp"
#include "hash.hpp"
#include "ifxconv.h"
#include "ifxsql.hpp"


/////////////////////////////////////////////////////////////////////////////
// API implementation

API(SQLResult) getInformixDriver(
	pCHAR pszVendorLib, pCHAR pszResourceFile, ppSQLDriver ppDrv)
{
	TRACE("getInformixDriver pszVendorLib=%s pszResourceFile=%s ppDrv=%p\n", pszVendorLib, pszResourceFile, ppDrv);

	CInformixDriver *pDrv = new CInformixDriver;
	if (pDrv == 0)
		return DBXERR_NOMEMORY;

	*ppDrv = pDrv;

	return SQL_SUCCESS;
}


/////////////////////////////////////////////////////////////////////////////
// CInformixDriver - SQLDriver implementation for Informix

CInformixDriver::CInformixDriver ()
{
	m_cbBlob = -1;
	m_pTRACEDesc = &m_TRACEDesc;
	m_pfCallBack = 0;
	m_ppfCallBack = &m_pfCallBack;
}

inline INT32 CInformixDriver::getBlobSize ()
{
	return m_cbBlob;
}

#ifdef __TRIAL__
CInformixConnection g_ifxConnection;
const int g_nMaxCommands = 10;
CInformixCommand g_ifxCommands[g_nMaxCommands];
int g_iCommand = 0;
#endif

SQLMETHODIMP CInformixDriver::getSQLConnection (ppSQLConnection ppConn)
{
	TRACE("CInformixDriver::getSQLConnection ppConn=%p\n", ppConn);

#ifdef __TRIAL__
	CInformixConnection *pConn = &g_ifxConnection;
	pConn->AddRef();
#else
	CInformixConnection *pConn = new CInformixConnection;
	if (pConn == 0)
		return DBXERR_NOMEMORY;
#endif

	pConn->setDriver(this);
	*ppConn = pConn;

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixDriver::setOption (eSQLDriverOption eDOption, INT32 PropValue)
{
	TRACE("CInformixDriver::setOption eDOption=%d PropValue=%d\n", eDOption, PropValue);

	switch (eDOption)
	{
	case eDrvBlobSize:
		{
			m_cbBlob = PropValue;
		}; break;
	case eDrvCallBack:
		{
			m_pfCallBack = reinterpret_cast<pfCallBack>(PropValue);
		}; break;
	case eDrvCallBackInfo:
		{
			m_TRACEDesc.iCBInfo = PropValue;
		}; break;
	case eDrvRestrict:
		{
		}; break;
	}
	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixDriver::getOption (eSQLDriverOption eDOption, pINT32 plValue, INT16 iMaxLength, pINT16 piLength)
{
	TRACE("CInformixDriver::getOption eDOption=%d plValue=%p iMaxLength=%hu piLength=%p\n", eDOption, plValue, iMaxLength, piLength);

	INT16 iLength;
	switch (eDOption)
	{
	case eDrvBlobSize:
		{
			iLength = sizeof(m_cbBlob);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			*plValue = m_cbBlob;
		}; break;
	case eDrvCallBack:
		{
			iLength = sizeof(pfCallBack);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			*plValue = reinterpret_cast<INT32>(m_pfCallBack);
		}; break;
	case eDrvCallBackInfo:
		{
			iLength = sizeof(m_TRACEDesc.iCBInfo);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			*plValue = m_TRACEDesc.iCBInfo;
		}; break;
	case eDrvRestrict:
		{
			iLength = 0;
		}; break;
	default:
		return DBXERR_NOTSUPPORTED;
	}
	*piLength = iLength;

	return SQL_SUCCESS;
}


/////////////////////////////////////////////////////////////////////////////
// CInformixError - Informix error managing object

CInformixError::CInformixError ()
{
	m_pszError = 0;
}

CInformixError::~CInformixError ()
{
	setError(0);
}

inline pCHAR CInformixError::getError ()
{
	return m_pszError;
}

void CInformixError::setError (pCHAR pszError)
{
	if (m_pszError != 0)
		free(m_pszError);

	if (pszError != 0)
		m_pszError = strdup(pszError);
	else
		m_pszError = 0;
}

SQLResult CInformixError::checkError (mint sqlcode)
{
	if (sqlcode < 0)
	{
		mint result;

		//
		// allocate SQL error message buffer
		//
		char *pszSQL = static_cast<char *>(malloc(MAX_ERROR_MSG_LEN));
		if (pszSQL == 0)
			return DBXERR_NOMEMORY;

		//
		// print SQL error number to message buffer
		//
		int sqlpos = sprintf(pszSQL, "SQL Error (%d) : ", sqlcode);

		//
		// get SQL error message with it's length
		//
		mint sqllen;
		result = rgetlmsg(sqlcode, &pszSQL[sqlpos], MAX_ERROR_MSG_LEN - sqlpos, &sqllen);

		//
		// if couldn't - report why
		//
		if (result != 0)
			sqllen = sprintf(&pszSQL[sqlpos], "Get SQL error message failure, reason %d", result);

		//
		// adjust message length
		//
		sqllen += sqlpos;

		//
		// save sqlca structure (sqlca is a macros of the function call)
		//
		sqlca_s *pca = &sqlca;

		//
		// check for ISAM error
		//
		char *pszISAM;
		mint isamlen;
		if (pca->sqlerrd[1] != 0)
		{
			//
			// allocate ISAM error message buufer
			//
			pszISAM = static_cast<char *>(malloc(MAX_ERROR_MSG_LEN));
			if (pszISAM == 0)
				return DBXERR_NOMEMORY;

			int isampos = sprintf(pszISAM, "ISAM Error (%ld) : ", pca->sqlerrd[1]);

			//
			// get ISAM error message
			//
			result = rgetlmsg(pca->sqlerrd[1], &pszISAM[isampos], MAX_ERROR_MSG_LEN - isampos, &isamlen);

			//
			// if couldn't - report why
			//
			if (result != 0)
				isamlen = sprintf(&pszISAM[isampos], "Get ISAM error message failure, reason %d", result);

			isamlen += isampos;
		}
		else
		{
			pszISAM = 0;
			isamlen = 0;
		}

		//
		// allocate memory for resulting error message
		//
		if (m_pszError)
			free(m_pszError);
		m_pszError = static_cast<char *>(malloc(sqllen + isamlen + 2*strlen(pca->sqlerrm) + 1));
		if (m_pszError == 0)
			return DBXERR_NOMEMORY;

		//
		// make final error message
		//
		if (pszSQL != 0)
			sqllen = sprintf(m_pszError, pszSQL, pca->sqlerrm);
		else
			sqllen = 0;
		if (pszISAM != 0)
			sprintf(&m_pszError[sqllen], pszISAM, pca->sqlerrm);


		//
		// free temporary message buffers
		//
		free(pszSQL);
		free(pszISAM);

		TRACE(m_pszError);
		traceMessage(traceERROR, m_pszError);

		return SQL_ERROR;
	}

	if (sqlcode == SQLNOTFOUND)
		return DBXERR_EOF;

	return SQL_SUCCESS;
}

inline SQLResult CInformixError::checkError ()
{
	return checkError(SQLCODE);
}


/////////////////////////////////////////////////////////////////////////////
// CErroredImpl

template <class T>
SQLMETHODIMP CErroredImpl<T>::getErrorMessage (pBYTE pszError)
{
	if (m_pszError != 0)
		memcpy(pszError, m_pszError, strlen(m_pszError) + 1);
	return SQL_SUCCESS;
}

template <class T>
SQLMETHODIMP CErroredImpl<T>::getErrorMessageLen (pUINT16 puErrorLen)
{
	if (m_pszError != 0)
		*puErrorLen = strlen(m_pszError) + 1;
	return SQL_SUCCESS;
}


/////////////////////////////////////////////////////////////////////////////
// CInformixConnection

CInformixConnection::CInformixConnection ()
{
	m_pDriver = 0;
	m_bAutoCommit = 1;
	m_cbBlob = -1;
	m_bWaitOnLocks = 0;
	m_bCommitRetain = 0;
	m_eTxnIsoLevel = xilREADCOMMITTED;
	m_pszHandle = 0;
	m_pfCallBack = 0;
	m_ppfCallBack = &m_pfCallBack;
	m_pTRACEDesc = &m_TRACEDesc;
	m_pszCharSet = 0;
	m_pszDatabase = m_pszUser = m_pszObjectName = "";
	memset(&m_sqlca, 0, sizeof(sqlca_s));
	m_bTrimChar = 0;
}

CInformixConnection::~CInformixConnection ()
{
	disconnect();
	if (m_pDriver != 0)
		m_pDriver->Release();
}

void CInformixConnection::setDriver (CInformixDriver *pDriver)
{
	m_pDriver = pDriver;
	m_pDriver->AddRef();
	m_cbBlob = pDriver->getBlobSize();
}

SQLResult CInformixConnection::checkAnsi (SQLResult sqlresult)
{
	if (m_bANSI && m_bAutoCommit != 0)
	{
		sqli_trans_commit();
		if (sqlresult == SQL_SUCCESS)
			sqlresult = checkError();
	}
	return sqlresult;
}

SQLResult CInformixConnection::checkCurrent ()
{
	//
	// if database name not present - nothing to set current
	//
	if (m_pszHandle == 0)
	{
		setError("Cannot set current for not connected");
		return SQL_ERROR;
	}

	//
	// set connection current
	//
	char *p = ifx_getcur_conn_name();
	if (p == 0 || strcmp(m_pszHandle, p) != 0)
	{
		sqli_connect_set(0, m_pszHandle, 0);

		SQLResult sqlresult = checkError();
		if (sqlresult != SQL_SUCCESS)
			return sqlresult;
	}

	return SQL_SUCCESS;
}

SQLResult CInformixConnection::checkDormant (SQLResult sqlresult)
{
	//
	// if database name not present - nothing to set dormant
	//
	if (m_pszHandle == 0)
	{
		setError("Cannot set dormant for not connected");
		return SQL_ERROR;
	}

	//
	// set connection dormant
	//
	sqli_connect_set(0, m_pszHandle, 1);

	//
	// report original error if it was
	//
	if (sqlresult == SQL_SUCCESS)
		sqlresult = checkError();

	return sqlresult;
}

inline INT32 CInformixConnection::getBlobSize ()
{
	return m_cbBlob;
}

inline int4 CInformixConnection::getCommitRetain ()
{
	return m_bCommitRetain;
}

inline BOOL CInformixConnection::getTrimChar ()
{
	return m_bTrimChar;
}

inline BOOL CInformixConnection::isNotInformixSE ()
{
	return m_bOnLine;
}

void CInformixConnection::save_sqlca ()
{
	m_sqlca = sqlca;
}

SQLResult CInformixConnection::applyWaitOnLocks ()
{
	if (m_bWaitOnLocks)
		return checkError(sqli_exec_immed("SET LOCK MODE TO WAIT"));
	else
		return checkError(sqli_exec_immed("SET LOCK MODE TO NOT WAIT"));
}

SQLResult CInformixConnection::applyTxnIsoLevel (bool bRetainUpdateLocks)
{
	//
	// choose isolation level option
	//
	char *pszIsolation;
	switch (m_eTxnIsoLevel)
	{
	case xilREADCOMMITTED:
		{
			pszIsolation = "COMMITTED READ";
		}; break;
	case xilREPEATABLEREAD:
		{
			pszIsolation = "REPEATABLE READ";
		}; break;
	case xilDIRTYREAD:
		{
			pszIsolation = "DIRTY READ";
		}; break;
	case xilCUSTOM:
		{
			pszIsolation = "CURSOR STABILITY";
		}; break;
	default:
		{
			pszIsolation = "COMMITTED READ";
		}
	}

	//
	// choose retain update locks option
	//
	char *pszRetainUpdateLocks;
	if (bRetainUpdateLocks)
		pszRetainUpdateLocks = " RETAIN UPDATE LOCKS";
	else
		pszRetainUpdateLocks = "";

	//
	// prepare SQL statement for set isolation level statement
	//
	char szSQL[64];
	sprintf(szSQL, "SET ISOLATION TO %s%s", pszIsolation, pszRetainUpdateLocks);

	//
	// execute prepared statement
	//
	return checkError(sqli_exec_immed(szSQL));
}

SQLMETHODIMP CInformixConnection::connect (
	pCHAR pszServerName, pCHAR pszUserName, pCHAR pszPassword)
{
	TRACE("CInformixConnection::connect pszServerName=%s pszUserName=%s\n", pszServerName, pszUserName);
	traceMessage(traceCONNECT, "connect to %s as %s", pszServerName, pszUserName);

	//
	// if database name present - already connected
	//
	if (m_pszHandle != 0)
	{
		setError("Already connected");
		return SQL_ERROR;
	}

	SQLResult sqlresult;

	//
	// generate connection name
	//
	char szName[18];
	sprintf(szName, "ifxcon_%p", this);

	//
	// locate database and server name
	//
	char *pszInfxServer = pszServerName;
	while ((*pszInfxServer != '\0') && (*pszInfxServer++ != '@'));

#ifdef WIN32
	//
	// fill login info structure
	//
	LoginInfoStructTag *login = &InetLogin;
	strncpy(login->InfxServer, pszInfxServer, sizeof(login->InfxServer));
	strncpy(login->User, pszUserName, sizeof(login->User));
	strncpy(login->Pass, pszPassword, sizeof(login->Pass));
	login->Client_Loc = m_pszCharSet;

	//
	// connect to specified database
	//
	sqli_connect_open(ESQLINTVERSION, 0, pszServerName, szName, 0, 1);
	save_sqlca();
	if ((sqlresult = checkError()) != SQL_SUCCESS)
		return sqlresult;
#else
	//
	// set INFROMIXSERVER environment variable
	//
	if (*pszInfxServer != '\0')
		setenv("INFORMIXSERVER", pszInfxServer, 1);

	//
	// prepare authentication structure for connect
	//
	ifx_user_t user = { pszUserName, pszPassword };
	ifx_conn_t conn = { IFX_CONN_TYPE_USER_AUTH, &user };

	//
	// set CLIENT_LOCALE environment variable
	//
	if (m_pszCharSet != 0)
		setenv("CLIENT_LOCALE", m_pszCharSet, 1);

	//
	// connect to specified database
	//
	sqli_connect_open(ESQLINTVERSION, 0, pszServerName, szName, &conn, 1);
	save_sqlca();
	if ((sqlresult = checkError()) != SQL_SUCCESS)
		return sqlresult;
#endif

	//
	// determain server features
	//
	m_bSupportsTransaction = 0;
	m_bANSI = false;
	m_bOnLine = 0;

	int cExceptions;
	ifx_hostvar_t hostVars[3];
	memset(hostVars, 0, sizeof(hostVars));
	sqli_mt_hostbind(ESQLINTVERSION, &hostVars[0], 1, CINTTYPE, 0, sizeof(cExceptions), 0);
	hostVars[0].hostaddr = reinterpret_cast<char *>(&cExceptions);
	sqli_diag_get(ESQLINTVERSION, hostVars, -1);

	for (int i = 1; i <= cExceptions; i++)
	{
		//
		// get SQLSTATE diagnostic value
		//
		char szSQLState[6];
		sqli_mt_hostbind(ESQLINTVERSION, &hostVars[0], 3, CCHARTYPE, 0, sizeof(szSQLState), 0);
		hostVars[0].hostaddr = szSQLState;
		sqli_diag_get(ESQLINTVERSION, hostVars, i);

		//
		// check if database supports transactions
		//
		if ((strcmp(szSQLState, "01I01") == 0) && (m_sqlca.sqlwarn.sqlwarn1 == 'W'))
			m_bSupportsTransaction = 1;
		//
		// check if database is in ANSI logging mode
		//
		else if ((strcmp(szSQLState, "01I03") == 0) && (m_sqlca.sqlwarn.sqlwarn2 == 'W'))
			m_bANSI = 1;
		//
		// check if server is not Informix-SE
		//
		else if ((strcmp(szSQLState, "01I04") == 0) && (m_sqlca.sqlwarn.sqlwarn3 == 'W'))
			m_bOnLine = 1;
	}

	//
	// save database name
	//
	m_pszHandle = strdup(szName);

	//
	// set lock mode
	//
	sqlresult = applyWaitOnLocks();

	return checkDormant(sqlresult);
}

SQLMETHODIMP CInformixConnection::disconnect ()
{
	//
	// if database name not present - nothing to disconnect
	//
	if (m_pszHandle == 0)
		return SQL_SUCCESS;

	TRACE("CInformixConnection::disconnect from %s\n", m_pszHandle);
	traceMessage(traceCONNECT, "disconnect");

	mint sqlcode;
	SQLResult sqlresult;

	//
	// make connection current
	//
	sqlresult = checkCurrent();
	if (sqlresult != SQL_SUCCESS)
		return sqlresult;

	//
	// disconnect from current connection
	//
	sqli_connect_close(3, 0, 0, 0);
	sqlcode = SQLCODE;
	sqlresult = checkError(sqlcode);

	//
	// if there is transaction active - commit or rollback it
	//
	if (sqlcode == -1800)
	{
		if (m_bAutoCommit)
			sqlresult = checkError(sqli_trans_commit());
		else
			sqlresult = checkError(sqli_trans_rollback());
		sqli_connect_close(3, 0, 0, 0);
		sqlresult = checkError();
	}

	//
	// free database name
	//
	free(m_pszHandle);
	m_pszHandle = 0;

	//
	// free charater set string
	//
	free(m_pszCharSet);
	m_pszCharSet = 0;

	save_sqlca();

	return sqlresult;
}

SQLMETHODIMP CInformixConnection::getSQLCommand (ppSQLCommand ppComm)
{
	TRACE("CInformixConnection::getSQLCommand ppComm=%p\n", ppComm);

#ifdef __TRIAL__
	if (g_iCommand == g_nMaxCommands)
	{
		setError("Commands limit exceeded");
		return SQL_ERROR;
	}
	CInformixCommand *pComm = &g_ifxCommands[g_iCommand++];

	pComm->AddRef();
#else
	CInformixCommand *pComm = new CInformixCommand;
	if (pComm == 0)
		return DBXERR_NOMEMORY;
#endif

	pComm->setConnection(this);
	*ppComm = pComm;

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixConnection::getSQLMetaData (ppSQLMetaData ppMeta)
{
	TRACE("CInformixConnection::getSQLMetaData ppMeta=%p\n", ppMeta);

	CInformixMetaData *pMeta = new CInformixMetaData;
	if (pMeta == 0)
		return DBXERR_NOMEMORY;

	pMeta->setConnection(this);
	*ppMeta = pMeta;

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixConnection::setOption (eSQLConnectOption eCOption, INT32 lValue)
{
	TRACE("CInformixConnection::setOption eCOption=%d lValue=%d\n", eCOption, lValue);

	switch (eCOption)
	{
	case eConnAutoCommit:
		{
			m_bAutoCommit = *reinterpret_cast<BOOL *>(&lValue);
		}; break;
	case eConnBlockingMode:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnBlobSize:
		{
			m_cbBlob = lValue;
		}; break;
	case eConnRoleName:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnWaitOnLocks:
		{
			m_bWaitOnLocks = *reinterpret_cast<BOOL *>(&lValue);
			if (m_pszHandle != 0)
			{
				SQLResult sqlresult = checkCurrent();
				if (sqlresult != SQL_SUCCESS)
					return sqlresult;
				sqlresult = applyWaitOnLocks();
				return checkDormant(sqlresult);
			}
		}; break;
	case eConnCommitRetain:
		{
			m_bCommitRetain = lValue ? 0x1000 : 0;
		}; break;
	case eConnTxnIsoLevel:
		{
			m_eTxnIsoLevel = *reinterpret_cast<eXILType *>(&lValue);
			if ((m_pszHandle != 0) && m_bOnLine)
			{
				SQLResult sqlresult = checkCurrent();
				if (sqlresult != SQL_SUCCESS)
					return sqlresult;
				sqlresult = applyTxnIsoLevel(false);
				return checkDormant(sqlresult);
			}
		}; break;
	case eConnNativeHandle:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnServerVersion:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnCallBack:
		{
			m_pfCallBack = reinterpret_cast<pfCallBack>(lValue);
		}; break;
	case eConnHostName:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnDatabaseName:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnCallBackInfo:
		{
			m_TRACEDesc.iCBInfo = lValue;
		}; break;
	case eConnObjectMode:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnMaxActiveComm:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnServerCharSet:
		{
			if (m_pszCharSet != 0)
				free(m_pszCharSet);
			m_pszCharSet = strdup(reinterpret_cast<char *>(lValue));
		}; break;
	case eConnSqlDialect:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;

	//
	// Added in Delphi7
	//
	case eConnRollbackRetain:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnObjectQuoteChar:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnConnectionName:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnOSAuthentication:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnSupportsTransaction:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnMultipleTransaction:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnServerPort:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnOnLine:
		{
			m_bOnLine = *reinterpret_cast<BOOL *>(&lValue);
		}; break;
	case eConnTrimChar:
		{
			m_bTrimChar = *reinterpret_cast<BOOL *>(&lValue);
		}; break;
	case eConnQualifiedName:
		{
			char *p = m_strQualifiedName = reinterpret_cast<char *>(lValue);

			// extract database name
			//
			while (*p != 0 && *p != '.' && *p != ':') p++;
			if (*p == ':')
			{
				if (p != m_strQualifiedName)
					m_pszDatabase = m_strQualifiedName;
				else
					m_pszDatabase = "";
				*p++ = 0;
				m_pszUser = p;
			}
			else
			{
				m_pszDatabase = "";
				m_pszUser = m_strQualifiedName;
			}

			// extract user name
			//
			while (*p != 0 && *p != '.') p++;
			if (*p == '.')
			{
				if (p == m_pszUser)
					m_pszUser = "";
				*p++ = 0;
				m_pszObjectName = p;
			}
			else
			{
				m_pszObjectName = m_pszUser;
				m_pszUser = "";
			}
		}; break;
	case eConnCatalogName:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnSchemaName:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnObjectName:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnQuotedObjectName:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnCustomInfo:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnTimeOut:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	default:
		return DBXERR_NOTSUPPORTED;
	}
	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixConnection::getOption (eSQLConnectOption eCOption, pINT32 plValue, INT16 iMaxLength, pINT16 piLength)
{
	TRACE("CInformixConnection::getOption eCOption=%d plValue=%p iMaxLength=%hu piLength=%p\n", eCOption, plValue, iMaxLength, piLength);

	INT16 iLength;
	switch (eCOption)
	{
	case eConnAutoCommit:
		{
			iLength = sizeof(m_bAutoCommit);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			*plValue = m_bAutoCommit;
		}; break;
	case eConnBlockingMode:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnBlobSize:
		{
			iLength = sizeof(m_cbBlob);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			*plValue = m_cbBlob;
		}; break;
	case eConnRoleName:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnWaitOnLocks:
		{
			iLength = sizeof(m_bWaitOnLocks);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			*plValue = m_bWaitOnLocks;
		}; break;
	case eConnCommitRetain:
		{
			iLength = sizeof(m_bCommitRetain);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			*plValue = m_bCommitRetain;
		}; break;
	case eConnTxnIsoLevel:
		{
			iLength = sizeof(m_eTxnIsoLevel);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			*plValue = m_eTxnIsoLevel;
		}; break;
	case eConnNativeHandle:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnServerVersion:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnCallBack:
		{
			iLength = sizeof(pfCallBack);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			*plValue = reinterpret_cast<INT32>(m_pfCallBack);
		}; break;
	case eConnHostName:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnDatabaseName:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnCallBackInfo:
		{
			iLength = sizeof(m_TRACEDesc.iCBInfo);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			*plValue = m_TRACEDesc.iCBInfo;
		}; break;
	case eConnObjectMode:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnMaxActiveComm:
		{
			iLength = sizeof(int);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			*plValue = -1;
		}; break;
	case eConnServerCharSet:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnSqlDialect:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;

	//
	// Added in Delphi7
	//
	case eConnRollbackRetain:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnObjectQuoteChar:
		{
			// Informix has no quote char for objects
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnConnectionName:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnOSAuthentication:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnSupportsTransaction:
		{
			iLength = sizeof(int);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			*plValue = m_bSupportsTransaction;
		}; break;
	case eConnMultipleTransaction:
		{
			iLength = sizeof(int);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			*plValue = 0;
		}; break;
	case eConnServerPort:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnOnLine:
		{
			iLength = sizeof(m_bOnLine);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			*plValue = m_bOnLine;
		}; break;
	case eConnTrimChar:
		{
			iLength = sizeof(m_bTrimChar);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			*plValue = m_bTrimChar;
		}; break;
	case eConnQualifiedName:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnCatalogName:
		{
			iLength = strlen(m_pszDatabase);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			strcpy(reinterpret_cast<char *>(plValue), m_pszDatabase);
		}; break;
	case eConnSchemaName:
		{
			iLength = strlen(m_pszUser);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			strcpy(reinterpret_cast<char *>(plValue), m_pszUser);
		}; break;
	case eConnObjectName:
		{
			iLength = strlen(m_pszObjectName);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			strcpy(reinterpret_cast<char *>(plValue), m_pszObjectName);
		}; break;
	case eConnQuotedObjectName:
		{
			// check required length
			//
			iLength = strlen(m_pszObjectName) + 1;
			if (*m_pszDatabase != 0)
				iLength += strlen(m_pszDatabase) + 1;
			if (*m_pszUser != 0)
				iLength += strlen(m_pszUser) + 2;
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;

			// make quoted name
			//
			if (*m_pszDatabase != 0)
			{
				strcpy(reinterpret_cast<char *>(plValue), m_pszDatabase);
				strcat(reinterpret_cast<char *>(plValue), ":");
			}
			else
				*reinterpret_cast<char *>(plValue) = 0;
			if (*m_pszUser != 0)
			{
				strcat(reinterpret_cast<char *>(plValue), "\"");
				strcat(reinterpret_cast<char *>(plValue), m_pszUser);
				strcat(reinterpret_cast<char *>(plValue), "\".");
			}
			strcat(reinterpret_cast<char *>(plValue), m_pszObjectName);
		}; break;
	case eConnCustomInfo:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eConnTimeOut:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	default:
		return DBXERR_NOTSUPPORTED;
	}

	*piLength = iLength;

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixConnection::beginTransaction (UINT32 ulTransDesc)
{
	TRACE("CInformixConnection::beginTransaction\n");
	traceMessage(traceTRANSACT, "BEGIN");

	SQLResult sqlresult;

	//
	// make connection current
	//
	sqlresult = checkCurrent();
	if (sqlresult != SQL_SUCCESS)
		return sqlresult;

	//
	// prepare transaction descritor pointer
	//
	pTransactionDesc pTD = reinterpret_cast<pTransactionDesc>(ulTransDesc);

	//
	// choose isolation level type
	//
	if (pTD != 0)
		m_eTxnIsoLevel = pTD->eTransIsoLevel;

	//
	// set isolation level type
	//
	if (m_bOnLine)
	{
		sqlresult = applyTxnIsoLevel((pTD != 0) && (pTD->uCustomIsolation != 0));
		if (sqlresult != SQL_SUCCESS)
			return sqlresult;
	}

	//
	// begin transaction
	//
	sqlresult = checkError(sqli_trans_begin());
	save_sqlca();

	//
	// make connection dormant
	//
	return checkDormant(sqlresult);
}

SQLMETHODIMP CInformixConnection::commit (UINT32 ulTransDesc)
{
	TRACE("CInformixConnection::commit\n");
	traceMessage(traceTRANSACT, "COMMIT");

	SQLResult sqlresult;

	//
	// make connection current
	//
	sqlresult = checkCurrent();
	if (sqlresult != SQL_SUCCESS)
		return sqlresult;

	//
	// commit transaction
	//
	sqlresult = checkError(sqli_trans_commit());
	save_sqlca();

	//
	// make connection dormant
	//
	return checkDormant(sqlresult);
}

SQLMETHODIMP CInformixConnection::rollback (UINT32 ulTransDesc)
{
	TRACE("CInformixConnection::rollback\n");
	traceMessage(traceTRANSACT, "ROLLBACK");

	SQLResult sqlresult;

	//
	// make connection current
	//
	sqlresult = checkCurrent();
	if (sqlresult != SQL_SUCCESS)
		return sqlresult;

	//
	// rollback transaction
	//
	sqlresult = checkError(sqli_trans_rollback());
	save_sqlca();

	//
	// make connection dormant
	//
	return checkDormant(sqlresult);
}

METHODIMP(sqlca_s *) CInformixConnection::get_sqlca ()
{
	return &m_sqlca;
}


/////////////////////////////////////////////////////////////////////////////
// CInformixCommand - SQLCommand implementation for Informix

CInformixCommand::CInformixCommand ()
{
	m_pConnection = 0;
	m_cRowset = 0;
	m_cbBlob = -1;
	m_bBlockRead = 0;
	m_pStmt = 0;
	m_pDesc = 0;
}

CInformixCommand::~CInformixCommand ()
{
	unprepare();

	if (m_pConnection != 0)
		m_pConnection->Release();
}

SQLResult CInformixCommand::unprepare ()
{
	SQLResult sqlresult;

	//
	// free statement
	//
	if (m_pStmt != 0)
	{
#ifdef __TRIAL__
		sqlresult = SQL_SUCCESS;
#else
	if ((sqlresult = checkCurrent()) == SQL_SUCCESS)
	{
		try
		{
			sqlresult = checkError(sqli_curs_free(ESQLINTVERSION, m_pStmt));
		}
		catch(...)
		{
			TRACE("CInformixCommand::unprepare command deallocation exception\n");
			setError("Command deallocation exception\n");
			traceMessage(traceERROR, m_pszError);
			sqlresult = SQL_ERROR;
		}

		//
		// make command connection dormant
		//
		sqlresult = checkDormant(sqlresult);
	}
#endif
		m_pStmt = 0;
	}
	else
		sqlresult = SQL_SUCCESS;

	//
	// free parameters descriptor
	//
	free_sqlda(m_pDesc, true);
	m_pDesc = 0;

	return sqlresult;
}

inline CInformixConnection * CInformixCommand::getConnection ()
{
	return m_pConnection;
}

void CInformixCommand::setConnection (CInformixConnection *pConnection)
{
	m_pConnection = pConnection;
	m_pConnection->AddRef();

	m_pTRACEDesc = m_pConnection->getTRACEDesc();
	m_ppfCallBack = m_pConnection->getCallBack();

	m_cbBlob = m_pConnection->getBlobSize();
}

SQLResult CInformixCommand::checkCurrent ()
{
	SQLResult sqlresult;

	sqlresult = m_pConnection->checkCurrent();
	if (sqlresult != SQL_SUCCESS)
		setError(m_pConnection->getError());

	return sqlresult;
}

SQLResult CInformixCommand::checkDormant (SQLResult sqlresult)
{
	if (sqlresult == SQL_SUCCESS)
	{
		sqlresult = m_pConnection->checkDormant(sqlresult);
		if (sqlresult != SQL_SUCCESS)
			setError(m_pConnection->getError());
	}
	else
		m_pConnection->checkDormant(sqlresult);

	return sqlresult;
}

inline INT32 CInformixCommand::getBlobSize ()
{
	return m_cbBlob;
}

inline ifx_cursor_t * CInformixCommand::getStmt ()
{
	return m_pStmt;
}

inline ifx_sqlda_t * CInformixCommand::getDesc ()
{
	return m_pDesc;
}

SQLMETHODIMP CInformixCommand::setOption (eSQLCommandOption eSOption, INT32 lValue)
{
	TRACE("CInformixCommand::setOption eSOption=%d lValue=%d\n", eSOption, lValue);

	switch (eSOption)
	{
	case eCommRowsetSize:
		{
			m_cRowset = lValue;
		}; break;
	case eCommBlobSize:
		{
			m_cbBlob = lValue;
		}; break;
	case eCommBlockRead:
		{
			m_bBlockRead = lValue;
		}; break;
	case eCommBlockWrite:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eCommParamCount:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eCommNativeHandle:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eCommCursorName:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eCommStoredProc:
		{
			if (lValue != 0)
				return DBXERR_NOTSUPPORTED;
		}; break;
	case eCommSQLDialect:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eCommTransactionID:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	}
	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCommand::getOption (eSQLCommandOption eSOption, pINT32 plValue, INT16 iMaxLength, pINT16 piLength)
{
	TRACE("CInformixCommand::getOption eSOption=%d plValue=%p iMaxLength=%hu piLength=%p\n", eSOption, plValue, iMaxLength, piLength);

	INT16 iLength;
	switch (eSOption)
	{
	case eCommRowsetSize:
		{
			iLength = sizeof(m_cRowset);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			*plValue = m_cRowset;
		}; break;
	case eCommBlobSize:
		{
			iLength = sizeof(m_cbBlob);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			*plValue = m_cbBlob;
		}; break;
	case eCommBlockRead:
		{
			iLength = sizeof(m_bBlockRead);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			*plValue = m_bBlockRead;
		}; break;
	case eCommBlockWrite:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eCommParamCount:
		{
			iLength = sizeof(m_pDesc->sqld);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			*plValue = m_pDesc->sqld;
		}; break;
	case eCommNativeHandle:
		{
			iLength = sizeof(m_pStmt);
			if (iMaxLength < iLength)
				return DBXERR_INVALIDLEN;
			*plValue = reinterpret_cast<INT32>(m_pStmt);
		}; break;
	case eCommCursorName:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eCommStoredProc:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eCommSQLDialect:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eCommTransactionID:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	default:
		return DBXERR_NOTSUPPORTED;
	}
	*piLength = iLength;

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCommand::setParameter (UINT16 uParameterNumber, UINT16 uChildPos, STMTParamType ePType, UINT16 uLogType, UINT16 uSubType, INT32 lMaxPrecision, INT32 lMaxScale, UINT32 ulLength, pVOID pBuffer, BOOL bIsNull)
{
	TRACE("CInformixCommand::setParameter uParameterNumber=%hu uChildPos=%hu ePType=%d uLogType=%hu uSubType=%hu lMaxPrecision=%d lMaxScale=%d ulLength=%d pBuffer=%p bIsNull=%d\n", uParameterNumber, uChildPos, ePType, uLogType, uSubType, lMaxPrecision, lMaxScale, ulLength, pBuffer, bIsNull);
	traceMessage(traceDATAIN, "set parameter number=%hu ptype=%d logtype=%hu subtype=%hu", uParameterNumber, ePType, uLogType, uSubType);

	//
	// allowed parameter type is only paramIN
	//
	if (ePType != paramIN)
	{
		setError("Allowed parameter type is only INPUT");
		return SQL_ERROR;
	}

	//
	// access to specified parameter
	//
	ifx_sqlvar_t *pvar = &m_pDesc->sqlvar[uParameterNumber - 1];

	//
	// must clear previous parameter value
	//
	clear_hostvar(pvar, true);

	//
	// convert logical parameter data type to informix data type
	//
	int2 sqltype;
	getInformix(uLogType, uSubType, &sqltype);
	if (sqltype == -1)
	{
		char szBuffer[100];
		sprintf(szBuffer, "Parameter %d: data type %d (subtype %d) not supported",
			uParameterNumber, uLogType, uSubType);
		setError(szBuffer);
		return SQL_ERROR;
	}
	pvar->sqltype = sqltype;

	//
	// set null indicator
	//
	short *psqlind = static_cast<short *>(malloc(sizeof(short)));
	if (psqlind == 0)
		return DBXERR_NOMEMORY;
	pvar->sqlind = psqlind;
	if ((*psqlind = bIsNull ? -1 : 0) != 0)
		return SQL_SUCCESS;

	//
	// fill parameter value
	//
	switch (sqltype)
	{
	case SQLDECIMAL:
		{
			// set precision and scale
			pvar->sqllen = 0x20FF;

			// allocate parameter value buffer
			decimal *pdec = static_cast<decimal *>(malloc(sizeof(decimal)));
			if (pdec == 0)
				return DBXERR_NOMEMORY;
			pvar->sqldata = reinterpret_cast<char *>(pdec);

			// convert between types
			bcd_to_decimal(reinterpret_cast<FMTBcd *>(pBuffer), pdec);
		}; break;
	case SQLDATE:
		{
			// set host variable length
			pvar->sqllen = sizeof(INT32);

			// allocate parameter value buffer
			pINT32 pint = static_cast<pINT32>(malloc(sizeof(INT32)));
			if (pint == 0)
				return DBXERR_NOMEMORY;
			pvar->sqldata = reinterpret_cast<char *>(pint);

			// evaluate value
			*pint = *reinterpret_cast<pINT32>(pBuffer) - DateDelta;
		}; break;
	case SQLDTIME:
		{
			// allocate parameter value buffer
			dtime *pdtime = static_cast<dtime *>(malloc(sizeof(dtime)));
			if (pdtime == 0)
				return DBXERR_NOMEMORY;
			pvar->sqldata = reinterpret_cast<char *>(pdtime);

			// convert between types
			if (uLogType == fldTIME)
			{
				// set length as qualifier
				pvar->sqllen = TU_ENCODE(11, TU_HOUR, TU_F3);
				time_to_dtime(*reinterpret_cast<pINT32>(pBuffer), pdtime);
			}
			else
			{
				// set length as qualifier
				pvar->sqllen = TU_CURRQUAL;
				if (uLogType == fldTIMESTAMP)
					timestamp_to_dtime(reinterpret_cast<pDFLOAT>(pBuffer), pdtime);
				else
					ctimestamp_to_dtime(reinterpret_cast<pCTIMESTAMP>(pBuffer), pdtime);
			}
		}; break;
	case SQLMONEY:
		{
			// set precision and scale
			pvar->sqllen = 0x1002;

			// allocate parameter value buffer
			decimal *pdec = static_cast<decimal *>(malloc(sizeof(decimal)));
			if (pdec == 0)
				return DBXERR_NOMEMORY;
			pvar->sqldata = reinterpret_cast<char *>(pdec);

			// convert between types
			deccvdbl(*reinterpret_cast<double *>(pBuffer), pdec);
		}; break;
	case SQLBYTES:
	case SQLTEXT:
		{
			// set host variable length to locator size
			pvar->sqllen = sizeof(loc_t);

			// allocate lob locator
			loc_t *ploc = static_cast<loc_t *>(malloc(sizeof(loc_t)));
			if (ploc == 0)
				return DBXERR_NOMEMORY;
			memset(ploc, 0, sizeof(loc_t));
			pvar->sqldata = reinterpret_cast<char *>(ploc);

			ploc->loc_loctype = LOCMEMORY;
			ploc->loc_bufsize = ulLength;
			ploc->loc_size = ulLength;
			ploc->loc_type = sqltype;

			// allocate lob buffer
			ploc->loc_buffer = static_cast<char *>(malloc(ulLength));
			if (ploc->loc_buffer == 0)
				return DBXERR_NOMEMORY;

			// copy lob value to buffer
			memcpy(ploc->loc_buffer, pBuffer, ulLength);
		}; break;
	default:
		{
			// set host variable length
			if ISSTRINGTYPE(sqltype)
				--ulLength;
			if ((pvar->sqllen = ulLength) > 0)
			{
				// allocate parameter value buffer
				pvar->sqldata = static_cast<char *>(malloc(ulLength));
				if (pvar->sqldata == 0)
					return DBXERR_NOMEMORY;

				// copy parameter value to buffer
				memcpy(pvar->sqldata, pBuffer, ulLength);
			}
		}
	}

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCommand::getParameter (UINT16 uParameterNumber, UINT16 uChildPos, pVOID pData, UINT32 ulLength, pINT32 plInd)
{
	TRACE("CInformixCommand::getParameter uParameterNumber=%hu uChildPos=%hu pData=%p ulLength=%d plInd=%d\n", uParameterNumber, uChildPos, pData, ulLength, plInd);
	traceMessage(traceDATAOUT, "get parameter number=%hu", uParameterNumber);

	return DBXERR_NOTSUPPORTED;
}

SQLMETHODIMP CInformixCommand::prepare (pCHAR pszSQL, UINT16 uParamCount)
{
	TRACE("CInformixCommand::prepare pszSQL=%s uParamCount=%hu\n", pszSQL, uParamCount);
	traceMessage(traceQPREPARE, "prepare %s", pszSQL);

	//
	// if already prepared - report error
	//
	if (m_pStmt != 0)
	{
		setError("Statement already prepared");
		return SQL_ERROR;
	}

	SQLResult sqlresult;

	//
	// make command connection current
	//
	sqlresult = checkCurrent();
	if (sqlresult != SQL_SUCCESS)
		return sqlresult;

	//
	// generate statement id and prepare it
	//
	char szName[18];
	sprintf(szName, "ifxstm_%p", &m_pStmt);
	m_pStmt = sqli_prep(ESQLINTVERSION, szName, pszSQL, 0, 0, -1, 0, 0);
	m_pConnection->save_sqlca();

	//
	// check prepare result
	//
	sqlresult = checkError();

	//
	// make command connection dormant
	//
	sqlresult = checkDormant(sqlresult);

	if (sqlresult != SQL_SUCCESS)
		return sqlresult;

	//
	// allocate memory for parameters descriptor
	//
	m_pDesc = static_cast<ifx_sqlda_t *>(malloc(sizeof(ifx_sqlda_t)));
	if (m_pDesc == 0)
		return DBXERR_NOMEMORY;
	memset(m_pDesc, 0, sizeof(ifx_sqlda_t));

	if (uParamCount > 0)
	{
		m_pDesc->sqld = uParamCount;
		m_pDesc->sqlvar = static_cast<ifx_sqlvar_t *>(malloc(uParamCount*sizeof(ifx_sqlvar_t)));
		if (m_pDesc->sqlvar == 0)
				return DBXERR_NOMEMORY;
		memset(m_pDesc->sqlvar, 0, uParamCount*sizeof(ifx_sqlvar_t));
	}

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCommand::execute (ppSQLCursor ppCur)
{
	TRACE("CInformixCommand::execute ppCur=%p\n", ppCur);
	traceMessage(traceQEXECUTE, "execute");

	if (m_pStmt == 0)
	{
		setError("Cannot execute not prepared statement");
		return SQL_ERROR;
	}

	SQLResult sqlresult;

	//
	// make command connection current
	//
	sqlresult = checkCurrent();
	if (sqlresult != SQL_SUCCESS)
		return sqlresult;

	if ((m_pStmt->_SQCstmttype == SQ_SELECT) || (m_pStmt->_SQCstmttype == SQ_EXECPROC))
	{
		//
		// create cursor for statement
		//
#ifdef __TRIM__
		CInformixCursor *pCur = new CInformixMetaDataCursor;
#else
		CInformixCursor *pCur = m_pConnection->getTrimChar() ? new CInformixMetaDataCursor : new CInformixCursor;
#endif
		if (pCur == 0)
			return DBXERR_NOMEMORY;

		sqlresult = pCur->setCommand(this);

		//
		// check for results 
		//
		if (sqlresult != SQL_NULL_DATA)
		{
			*ppCur = pCur;

			m_pConnection->save_sqlca();
			m_lRowsAffected = m_pConnection->get_sqlca()->sqlerrd[2];

			//
			// make command connection dormant
			//
			// !!! sqlresult = checkDormant(sqlresult);

			return sqlresult;
		}

		delete pCur;
	}

	//
	// execute statement
	//
	sqlresult = checkError(sqli_exec(ESQLINTVERSION, m_pStmt, m_pDesc, 0, 0, 0, 0, 0, 0));
	m_pConnection->save_sqlca();
	m_lRowsAffected = m_pConnection->get_sqlca()->sqlerrd[2];
	if (sqlresult == DBXERR_EOF && m_lRowsAffected == 0)
		sqlresult = 0;

	//
	// make command connection dormant
	//
	return checkDormant(m_pConnection->checkAnsi(sqlresult));
}

SQLMETHODIMP CInformixCommand::executeImmediate (pCHAR pszSQL, ppSQLCursor ppCur)
{
	TRACE("CInformixCommand::executeImmediate pszSQL=%s ppCur=%p\n", pszSQL, ppCur);
	traceMessage(traceQEXECUTE, "execute immediate");

	SQLResult sqlresult;

	//
	// make command connection current
	//
	sqlresult = checkCurrent();
	if (sqlresult != SQL_SUCCESS)
		return sqlresult;

	//
	// execute statement immediate (this statement doesn't return cursor)
	//
	sqlresult = checkError(sqli_exec_immed(pszSQL));
	m_pConnection->save_sqlca();
	m_lRowsAffected = m_pConnection->get_sqlca()->sqlerrd[2];
	if (sqlresult == DBXERR_EOF && m_lRowsAffected == 0)
		sqlresult = 0;

	//
	// make command connection dormant
	//
	return checkDormant(m_pConnection->checkAnsi(sqlresult));
}

SQLMETHODIMP CInformixCommand::getNextCursor (ppSQLCursor ppCur)
{
	TRACE("CInformixCommand::getNextCursor ppCur=%p\n", ppCur);

	*ppCur = 0;

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCommand::getRowsAffected (pINT32 plRows)
{
	TRACE("CInformixCommand::getRowsAffected plRows=%p\n", plRows);

	*plRows = m_lRowsAffected;

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCommand::close ()
{
	TRACE("CInformixCommand::close\n");

	//
	// closing prepared resources couse to error for second execution
	// due to Borland's bug in TCustomSQLDataSet.ExecSQL reailztion
	//

	/* 
	return unprepare();
	*/

	return SQL_SUCCESS;
}


/////////////////////////////////////////////////////////////////////////////
// CInformixCursor - SQLCursor implementation for Informix

CInformixCursor::CInformixCursor ()
{
	m_pCommand = 0;
	m_pCurs = 0;
	m_pDesc = 0;
	m_pColumns = 0;
	m_ppautolocs = 0;
}

CInformixCursor::~CInformixCursor ()
{
	//
	// close and free cursor
	//
	if (m_pCurs != 0)
	{
		// make cursor connection dormant
		if (checkCurrent() == SQL_SUCCESS)
		{
			try
			{
				if (sqli_curs_close(ESQLINTVERSION, m_pCurs) >= 0)
					sqli_curs_free(ESQLINTVERSION, m_pCurs);
			}
			catch(...)
			{
				TRACE("CInformixCursor::~CInformixCursor cursor deallocation exception\n");
				traceMessage(traceERROR, "cursor deallocation exception\n");
			}

			m_pCommand->getConnection()->checkAnsi(SQL_SUCCESS);

			// make cursor connection dormant
			checkDormant(SQL_SUCCESS);
		}
	}

	//
	// free fetch descriptor
	//
	free_sqlda(m_pDesc, m_ppautolocs == 0);

	//
	// free columns descriptions
	//
	free(m_pColumns);

	//
	// free locators pointers
	//
	free(m_ppautolocs);

	if (m_pCommand != 0)
		m_pCommand->Release();
}

SQLResult CInformixCursor::describeColumn (char *pDesc, int col_num, ifx_sqlvar_t *pvar, ColumnDesc *pCol)
{
	SQLResult sqlresult;

	ifx_hostvar_t hostVars[7];
	memset(hostVars, 0, sizeof(hostVars));

	//
	// get columns description
	//
	sqli_mt_hostbind(ESQLINTVERSION, &hostVars[0], XSQLNAME, CCHARTYPE, 0, sizeof(pCol->szName), 0);
	hostVars[0].hostaddr = pCol->szName;
	sqli_mt_hostbind(ESQLINTVERSION, &hostVars[1], XSQLTYPE, CSHORTTYPE, 0, sizeof(pvar->sqltype), 0);
	hostVars[1].hostaddr = reinterpret_cast<char *>(&pvar->sqltype);
	sqli_mt_hostbind(ESQLINTVERSION, &hostVars[2], XSQLLEN, CINTTYPE, 0, sizeof(pvar->sqllen), 0);
	hostVars[2].hostaddr = reinterpret_cast<char *>(&pvar->sqllen);
	sqli_mt_hostbind(ESQLINTVERSION, &hostVars[3], XSQLPRECISION, CSHORTTYPE, 0, sizeof(pCol->iPrecision), 0);
	hostVars[3].hostaddr = reinterpret_cast<char *>(&pCol->iPrecision);
	sqli_mt_hostbind(ESQLINTVERSION, &hostVars[4], XSQLSCALE, CSHORTTYPE, 0, sizeof(pCol->iScale), 0);
	hostVars[4].hostaddr = reinterpret_cast<char *>(&pCol->iScale);
	sqli_mt_hostbind(ESQLINTVERSION, &hostVars[5], XSQLNULLABLE, CINTTYPE, 0, sizeof(pCol->bNullable), 0);
	hostVars[5].hostaddr = reinterpret_cast<char *>(&pCol->bNullable);
	sqlresult = m_pCommand->checkError(sqli_desc_get(ESQLINTVERSION, pDesc, col_num, hostVars, 0));
	if (sqlresult != SQL_SUCCESS)
			return sqlresult;

	//
	// trim column name
	//
	trimString(pCol->szName);
	pvar->sqlname = pCol->szName;

	//
	// convert informix type into logical
	//
	getLogical(pvar->sqltype, &pCol->uLogType, &pCol->uSubType);
	if (pCol->uLogType == fldUNKNOWN)
	{
		char szBuffer[256];
		sprintf(szBuffer, "Field '%s' is unsupported data type '%s'", pCol->szName, rtypname(pvar->sqltype));
		m_pCommand->setError(szBuffer);
		return SQL_ERROR;
	}

	//
	// for INTERVAL type convert it to CHAR(28)
	//
	if (pvar->sqltype == SQLINTERVAL)
	{
		pvar->sqltype = SQLCHAR;
		pvar->sqllen = 28;
	}

	//
	// calculate size for data
	//
	pvar->sqldata = static_cast<char *>(malloc(rtypmsize(pvar->sqltype, pvar->sqllen)));
	if (pvar->sqldata == 0)
		return DBXERR_NOMEMORY;

	//
	// adjust additional attributes
	//
	switch (pCol->uLogType)
	{
	case fldBCD:
	case fldFMTBCD:
		{
			// !!! Before SP1 Delphi incorrect convert to string
			//if (pCol->iScale == 0xff)
			//      pCol->iScale = pCol->iPrecision;
			if (pCol->iScale == 0xff)
			{
				pCol->iPrecision = 32;
				pCol->iScale = 16;
			}
		}; break;
	case fldZSTRING:
		{
			pCol->iPrecision = static_cast<INT16>(pvar->sqllen);
			pvar->sqllen += 1;
		}; break;
	case fldBLOB:
		{
			loc_t *ploc = reinterpret_cast<loc_t *>(pvar->sqldata);

			//
			// set locator attributes
			//
			ploc->loc_loctype = LOCMEMORY;
			ploc->loc_bufsize = m_pCommand->getBlobSize();
			if (ploc->loc_bufsize > 0)
			{
				ploc->loc_buffer = static_cast<char *>(malloc(ploc->loc_bufsize));
				if (ploc->loc_buffer == 0)
					return DBXERR_NOMEMORY;
			}
			else
			{
				m_ppautolocs[m_cautolocs++] = ploc;
				ploc->loc_buffer = 0;
			}
			ploc->loc_mflags = 0;
			ploc->loc_oflags = 0;
		}; break;
	}

	pCol->bAutoIncrement = ISSERIALTYPE(pvar->sqltype);
	pCol->bReadOnly = 0;
	pCol->bSearchable = 0;
	pCol->bBlobExactSize = 1;

	pvar->sqlind = static_cast<short *>(malloc(sizeof(pvar->sqlind)));
	if (pvar->sqlind == 0)
		return DBXERR_NOMEMORY;

	return SQL_SUCCESS;
}

SQLResult CInformixCursor::loadColumns ()
{
	SQLResult sqlresult;

	//
	// allocate statement sql descriptor
	//
	char szDesc[18];
	sprintf(szDesc, "ifxdsc_%p", &szDesc);

	sqlda *ptr;
	sqlresult = m_pCommand->checkError(sqli_describe_stmt(ESQLINTVERSION, m_pCommand->getStmt(), &ptr, 0));
	if (sqlresult != SQL_SUCCESS)
		return sqlresult;

	//
	// if columns count is 0 then return to command SQL_NULL_DATA
	//
	if (ptr->sqld > 0)
		sqlresult = m_pCommand->checkError(sqli_desc_alloc(szDesc, ptr->sqld));
	else
		sqlresult = SQL_NULL_DATA;

#ifdef WIN32
	SqlFreeMem(ptr, SQLDA_FREE);
#else
	free(ptr);
#endif
	if (sqlresult != SQL_SUCCESS)
		return sqlresult;

	//
	// describe statement using sql descriptor
	//
	sqlresult = m_pCommand->checkError(sqli_describe_stmt(ESQLINTVERSION, m_pCommand->getStmt(), 0, szDesc));
	if (sqlresult != SQL_SUCCESS)
	{
		sqli_desc_dealloc(szDesc);
		return sqlresult;
	}

	//
	// allocate fetch descriptor
	//
	m_pDesc = static_cast<ifx_sqlda_t *>(malloc(sizeof(ifx_sqlda_t)));
	if (m_pDesc == 0)
	{
		sqli_desc_dealloc(szDesc);
		return DBXERR_NOMEMORY;
	}
	memset(m_pDesc, 0, sizeof(ifx_sqlda_t));

	ifx_hostvar_t hostVars[2];
	memset(hostVars, 0, sizeof(hostVars));

	//
	// get columns count
	//
	sqli_mt_hostbind(ESQLINTVERSION, &hostVars[0], XSQLD, CSHORTTYPE, 0, sizeof(m_pDesc->sqld), 0);
	hostVars[0].hostaddr = reinterpret_cast<char *>(&m_pDesc->sqld);
	sqlresult = m_pCommand->checkError(sqli_desc_get(ESQLINTVERSION, szDesc, -1, hostVars, 0));
	if (sqlresult != SQL_SUCCESS)
	{
		sqli_desc_dealloc(szDesc);
		return sqlresult;
	}

	//
	// allocate memory for columns descriptions
	//
	m_pDesc->sqlvar = static_cast<ifx_sqlvar_t *>(malloc(m_pDesc->sqld*sizeof(ifx_sqlvar_t)));
	if (m_pDesc->sqlvar == 0)
	{
		sqli_desc_dealloc(szDesc);
		return DBXERR_NOMEMORY;
	}
	memset(m_pDesc->sqlvar, 0, m_pDesc->sqld*sizeof(ifx_sqlvar_t));

	m_pColumns = static_cast<ColumnDesc *>(malloc(m_pDesc->sqld*sizeof(ColumnDesc)));
	if (m_pColumns == 0)
	{
		sqli_desc_dealloc(szDesc);
		return DBXERR_NOMEMORY;
	}

	//
	// initialize auto locators pointers
	//
	m_cautolocs = 0;
	if (m_pCommand->getBlobSize() == -1)
	{
		m_ppautolocs = static_cast<loc_t **>(malloc(m_pDesc->sqld*sizeof(loc_t *)));
		if (m_ppautolocs == 0)
		{
			sqli_desc_dealloc(szDesc);
			return DBXERR_NOMEMORY;
		}
	}

	//
	// get columns descriptions, allocate fetch buffers and count lobs
	//
	ifx_sqlvar_t *pvar = m_pDesc->sqlvar;
	ColumnDesc *pCol = m_pColumns;
	for (UINT16 i = 1; i <= m_pDesc->sqld; i++, pvar++, pCol++)
	{
		sqlresult = describeColumn(szDesc, i, pvar, pCol);
		if (sqlresult != SQL_SUCCESS)
		{
			sqli_desc_dealloc(szDesc);
			return sqlresult;
		}
	}

	//
	// free command statement sql descriptor
	//
	sqlresult = m_pCommand->checkError(sqli_desc_dealloc(szDesc));

	return sqlresult;
}

SQLResult CInformixCursor::setCommand (CInformixCommand *pCommand)
{
	m_pCommand = pCommand;
	m_pCommand->AddRef();

	SQLResult sqlresult;

	//
	// load columns definitions
	//
	sqlresult = loadColumns();
	if (sqlresult != SQL_SUCCESS)
		return sqlresult;

	//
	// generate cursor name and allocate it
	//
	char szName[18];
	sprintf(szName, "ifxcur_%p", &m_pCurs);
	m_pCurs = sqli_curs_locate(ESQLINTVERSION, szName, 512);
	sqlresult = m_pCommand->checkError();
	if (sqlresult != SQL_SUCCESS)
		return sqlresult;

	//
	// declare cursor for command statement
	//
	sqlresult = m_pCommand->checkError(sqli_curs_decl_dynm(ESQLINTVERSION, m_pCurs, 0, m_pCommand->getStmt(), pCommand->getConnection()->getCommitRetain(), 0));
	if (sqlresult != SQL_SUCCESS)
		return sqlresult;

	//
	// open declared cursor
	//
	sqlresult = m_pCommand->checkError(sqli_curs_open(ESQLINTVERSION, m_pCurs, m_pCommand->getDesc(), 0, 0, 0, 0));
	if (sqlresult != SQL_SUCCESS)
		return sqlresult;

	m_pTRACEDesc = m_pCommand->getTRACEDesc();
	m_ppfCallBack = m_pCommand->getCallBack();

	return SQL_SUCCESS;
}

SQLResult CInformixCursor::checkCurrent ()
{
	SQLResult sqlresult;

	sqlresult = m_pCommand->getConnection()->checkCurrent();
	if (sqlresult != SQL_SUCCESS)
		setError(m_pCommand->getConnection()->getError());

	return sqlresult;
}

SQLResult CInformixCursor::checkDormant (SQLResult sqlresult)
{
	CInformixConnection *pConnection = m_pCommand->getConnection();

	if (sqlresult == SQL_SUCCESS)
	{
		sqlresult = pConnection->checkDormant(sqlresult);
		if (sqlresult != SQL_SUCCESS)
			setError(pConnection->getError());
	}
	else
		pConnection->checkDormant(sqlresult);

	return sqlresult;
}

inline ifx_sqlvar_t * CInformixCursor::getFields ()
{
	return m_pDesc->sqlvar;
}

SQLMETHODIMP CInformixCursor::setOption (eSQLCursorOption eCurOption, INT32 lValue)
{
	TRACE("CInformixCursor::setOption eCurOption=%d lValue=%d\n", eCurOption, lValue);

	return DBXERR_NOTSUPPORTED;
}

SQLMETHODIMP CInformixCursor::getOption (eSQLCursorOption eCurOption, pINT32 plValue, INT16 iMaxLength, pINT16 piLength)
{
	TRACE("CInformixCursor::getOption eCurOption=%d plValue=%p iMaxLength=%hu piLength=%p\n", eCurOption, plValue, iMaxLength, piLength);

	return DBXERR_NOTSUPPORTED;
}

SQLMETHODIMP CInformixCursor::getColumnCount (pUINT16 puColumns)
{
	TRACE("CInformixCursor::getColumnCount puColumns=%p\n", puColumns);

	*puColumns = m_pDesc->sqld;

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCursor::getColumnNameLength (UINT16 uColumnNumber, pUINT16 puLen)
{
	TRACE("CInformixCursor::getColumnNameLength uColumnNumber=%d puLen=%p\n", uColumnNumber, puLen);

	*puLen = strlen(m_pColumns[uColumnNumber - 1].szName);

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCursor::getColumnName (UINT16 uColumnNumber, pCHAR pColumnName)
{
	TRACE("CInformixCursor::getColumnName uColumnNumber=%d pColumnName=%p\n", uColumnNumber, pColumnName);

	strcpy(pColumnName, m_pColumns[uColumnNumber - 1].szName);

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCursor::getColumnType (UINT16 uColumnNumber, pUINT16 puLogType, pUINT16 puSubType)
{
	TRACE("CInformixCursor::getColumnType uColumnNumber=%d puLogType=%p puSubType=%p\n", uColumnNumber, puLogType, puSubType);

	ColumnDesc *pDesc = &m_pColumns[uColumnNumber - 1];

	*puLogType = pDesc->uLogType;
	*puSubType = pDesc->uSubType;

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCursor::getColumnLength (UINT16 uColumnNumber, pUINT32 pulLength)
{
	TRACE("CInformixCursor::getColumnLength uColumnNumber=%d pulLength=%p\n", uColumnNumber, pulLength);

	*pulLength = m_pDesc->sqlvar[uColumnNumber - 1].sqllen;

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCursor::getColumnPrecision (UINT16 uColumnNumber, pINT16 piPrecision)
{
	TRACE("CInformixCursor::getColumnPrecision uColumnNumber=%d piPrecision=%p\n", uColumnNumber, piPrecision);

	*piPrecision = m_pColumns[uColumnNumber - 1].iPrecision;

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCursor::getColumnScale (UINT16 uColumnNumber, pINT16 piScale)
{
	TRACE("CInformixCursor::getColumnScale uColumnNumber=%d piScale=%p\n", uColumnNumber, piScale);

	*piScale = m_pColumns[uColumnNumber - 1].iScale;

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCursor::isNullable (UINT16 uColumnNumber, pBOOL pbNullable)
{
	TRACE("CInformixCursor::isNullable uColumnNumber=%d pbNullable=%p\n", uColumnNumber, pbNullable);

	*pbNullable = m_pColumns[uColumnNumber - 1].bNullable;

	return SQL_SUCCESS;
}


SQLMETHODIMP CInformixCursor::isAutoIncrement (UINT16 uColumnNumber, pBOOL pbAutoIncrement)
{
	TRACE("CInformixCursor::isAutoIncrement uColumnNumber=%d pbAutoIncrement=%p\n", uColumnNumber, pbAutoIncrement);

	*pbAutoIncrement = m_pColumns[uColumnNumber - 1].bAutoIncrement;

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCursor::isReadOnly (UINT16 uColumnNumber, pBOOL pbReadOnly)
{
	TRACE("CInformixCursor::isReadOnly uColumnNumber=%d pbReadOnly=%p\n", uColumnNumber, pbReadOnly);

	*pbReadOnly = m_pColumns[uColumnNumber - 1].bReadOnly;

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCursor::isSearchable (UINT16 uColumnNumber, pBOOL pbSearchable)
{
	TRACE("CInformixCursor::isSearchable uColumnNumber=%d pbSearchable=%p\n", uColumnNumber, pbSearchable);

	*pbSearchable = m_pColumns[uColumnNumber - 1].bSearchable;

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCursor::isBlobSizeExact (UINT16 uColumnNumber, pBOOL pbBlobExactSize)
{
	TRACE("CInformixCursor::isBlobSizeExact uColumnNumber=%d pbBlobExactSize=%p\n", uColumnNumber, pbBlobExactSize);

	*pbBlobExactSize = m_pColumns[uColumnNumber - 1].bBlobExactSize;

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCursor::next ()
{
	TRACE("CInformixCursor::next\n");
	traceMessage(traceSTMT, "fetch");

	SQLResult sqlresult;

	//
	// make cursor connection current
	//
	sqlresult = m_pCommand->checkCurrent();
	if (sqlresult != SQL_SUCCESS)
		return sqlresult;

	//
	// fetch one record
	//
	static _FetchSpec _FS0 = { 0, 1, 0 };
	sqlresult = checkError(sqli_curs_fetch(ESQLINTVERSION, m_pCurs, 0, m_pDesc, 0, &_FS0));

	//
	// make cursor connection dormant
	//
	// !!! if (sqlresult == DBXERR_EOF)
		// !!! sqlresult = checkDormant(sqlresult);

	if (sqlresult != SQL_SUCCESS)
	{
		m_pCommand->setError(this->getError());
		return sqlresult;
	}

	for (int i = 0; i < m_cautolocs; i++)
	{
		loc_t *ploc = m_ppautolocs[i];
		if (ploc != 0)
			if (ploc->loc_bufsize > 0)
			{
				ploc->loc_mflags = LOC_ALLOC;
				m_ppautolocs[i] = 0;
			}
			else
				ploc->loc_bufsize = -1;
	}

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCursor::getString (UINT16 uColumnNumber, pCHAR pData, pBOOL pbIsNull)
{
	TRACE("CInformixCursor::getString uColumnNumber=%d pData=%p pbIsNull=%p\n", uColumnNumber, pData, pbIsNull);

	ifx_sqlvar_t *pvar = &m_pDesc->sqlvar[uColumnNumber - 1];

	if (((*pbIsNull = *pvar->sqlind) == 0) && (pData != 0))
		strcpy(pData, pvar->sqldata);

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCursor::getShort (UINT16 uColumnNumber, pINT16 pData, pBOOL pbIsNull)
{
	TRACE("CInformixCursor::getShort uColumnNumber=%d pData=%p pbIsNull=%p\n", uColumnNumber, pData, pbIsNull);

	ifx_sqlvar_t *pvar = &m_pDesc->sqlvar[uColumnNumber - 1];

	if (((*pbIsNull = *pvar->sqlind) == 0) && (pData != 0))
		*pData = *reinterpret_cast<pINT16>(pvar->sqldata);

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCursor::getLong (UINT16 uColumnNumber, pINT32 pData, pBOOL pbIsNull)
{
	TRACE("CInformixCursor::getLong uColumnNumber=%d pData=%p pbIsNull=%p\n", uColumnNumber, pData, pbIsNull);

	ifx_sqlvar_t *pvar = &m_pDesc->sqlvar[uColumnNumber - 1];

	if (((*pbIsNull = *pvar->sqlind) == 0) && (pData != 0))
		*pData = *reinterpret_cast<pINT32>(pvar->sqldata);

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCursor::getDouble (UINT16 uColumnNumber, pDFLOAT pData, pBOOL pbIsNull)
{
	TRACE("CInformixCursor::getDouble uColumnNumber=%d pData=%p pbIsNull=%p\n", uColumnNumber, pData, pbIsNull);

	ifx_sqlvar_t *pvar = &m_pDesc->sqlvar[uColumnNumber - 1];

	if (((*pbIsNull = *pvar->sqlind) == 0) && (pData != 0))
		switch (pvar->sqltype)
		{
		case SQLFLOAT:
			{
				*pData = *reinterpret_cast<pDFLOAT>(pvar->sqldata);
			}; break;
		case SQLMONEY:
			{
				dectodbl(reinterpret_cast<decimal *>(pvar->sqldata), pData);
			}; break;
		case SQLSMFLOAT:
			{
				*pData = *reinterpret_cast<pFLOAT>(pvar->sqldata);
			}; break;
		default:
			return DBXERR_NOTSUPPORTED;
		}

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCursor::getBcd (UINT16 uColumnNumber, pFMTBcd pData, pBOOL pbIsNull)
{
	TRACE("CInformixCursor::getBcd uColumnNumber=%d pData=%p pbIsNull=%p\n", uColumnNumber, pData, pbIsNull);

	ifx_sqlvar_t *pvar = &m_pDesc->sqlvar[uColumnNumber - 1];

	if (((*pbIsNull = *pvar->sqlind) == 0) && (pData != 0))
	{
		ColumnDesc *pcol = &m_pColumns[uColumnNumber - 1];
		decimal_to_bcd(reinterpret_cast<decimal *>(pvar->sqldata), reinterpret_cast<FMTBcd *>(pData), pcol->iPrecision, pcol->iScale);
	}

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCursor::getTimeStamp (UINT16 uColumnNumber, pCTIMESTAMP pData, pBOOL pbIsNull)
{
	TRACE("CInformixCursor::getTimeStamp uColumnNumber=%d pData=%p pbIsNull=%p\n", uColumnNumber, pData, pbIsNull);

	ifx_sqlvar_t *pvar = &m_pDesc->sqlvar[uColumnNumber - 1];

	if (((*pbIsNull = *pvar->sqlind) == 0) && (pData != 0))
		dtime_to_ctimestamp(reinterpret_cast<dtime *>(pvar->sqldata), pData);

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCursor::getTime (UINT16 uColumnNumber, pINT32 pData, pBOOL pbIsNull)
{
	TRACE("CInformixCursor::getTime uColumnNumber=%d pData=%p pbIsNull=%p\n", uColumnNumber, pData, pbIsNull);

	ifx_sqlvar_t *pvar = &m_pDesc->sqlvar[uColumnNumber - 1];

	if (((*pbIsNull = *pvar->sqlind) == 0) && (pData != 0))
		*pData = *reinterpret_cast<pINT32>(pvar->sqldata);

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCursor::getDate (UINT16 uColumnNumber, pINT32 pData, pBOOL pbIsNull)
{
	TRACE("CInformixCursor::getDate uColumnNumber=%d pData=%p pbIsNull=%p\n", uColumnNumber, pData, pbIsNull);

	ifx_sqlvar_t *pvar = &m_pDesc->sqlvar[uColumnNumber - 1];

	if (((*pbIsNull = *pvar->sqlind) == 0) && (pData != 0))
		*pData = *reinterpret_cast<pINT32>(pvar->sqldata) + DateDelta;

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCursor::getBytes (UINT16 uColumnNumber, pBYTE pData, pBOOL pbIsNull)
{
	TRACE("CInformixCursor::getBytes uColumnNumber=%d pData=%p pbIsNull=%p\n", uColumnNumber, pData, pbIsNull);

	return DBXERR_NOTSUPPORTED;
}

SQLMETHODIMP CInformixCursor::getBlobSize (UINT16 uColumnNumber, pUINT32 pulLength, pBOOL pbIsNull)
{
	TRACE("CInformixCursor::getBlobSize uColumnNumber=%d pulLength=%p pbIsNull=%p\n", uColumnNumber, pulLength, pbIsNull);

	ifx_sqlvar_t *pvar = &m_pDesc->sqlvar[uColumnNumber - 1];

	if (((*pbIsNull = *pvar->sqlind) == 0) && (pulLength != 0))
		*pulLength = reinterpret_cast<loc_t *>(pvar->sqldata)->loc_size;

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixCursor::getBlob (UINT16 uColumnNumber, pVOID pData, pBOOL pbIsNull, UINT32 ulLength)
{
	TRACE("CInformixCursor::getBlob uColumnNumber=%d pData=%p pbIsNull=%p ulLength=%d\n", uColumnNumber, pData, pbIsNull, ulLength);

	ifx_sqlvar_t *pvar = &m_pDesc->sqlvar[uColumnNumber - 1];

	if (((*pbIsNull = *pvar->sqlind) == 0) && (pData != 0))
	{
		traceMessage(traceBLOB, "get blob data from column=%d", uColumnNumber);
		memcpy(pData, reinterpret_cast<loc_t *>(pvar->sqldata)->loc_buffer, ulLength);
	}

	return SQL_SUCCESS;
}


/////////////////////////////////////////////////////////////////////////////
// CInformixMetaData

SQLMETHODIMP CInformixMetaDataCursor::getString (UINT16 uColumnNumber, pCHAR pData, pBOOL pbIsNull)
{
	TRACE("CInformixMetaDataCursor::getString uColumnNumber=%d pData=%p pbIsNull=%p\n", uColumnNumber, pData, pbIsNull);

	ifx_sqlvar_t *pvar = &getFields()[uColumnNumber - 1];

	if (((*pbIsNull = *pvar->sqlind) == 0) && (pData != 0))
	{
		strcpy(pData, pvar->sqldata);
		trimString(pData);
	}

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixTablesCursor::next ()
{
	TRACE("CInformixTablesCursor::next");

	SQLResult sqlresult;

	sqlresult = CInformixMetaDataCursor::next();
	if (sqlresult != SQL_SUCCESS)
		return sqlresult;

	ifx_sqlvar_t *pfields = getFields();

	INT32 *ptable_type = reinterpret_cast<INT32 *>(pfields[TablesSQL_table_type].sqldata);
	switch (*pfields[TablesSQL_tabtype].sqldata)
	{
	case 'T': *ptable_type = eSQLTable; break;
	case 'V': *ptable_type = eSQLView; break;
	case 'S': *ptable_type = eSQLSynonym; break;
	}

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixColumnsCursor::next ()
{
	TRACE("CInformixColumnsCursor::next");

	SQLResult sqlresult;

	sqlresult = CInformixMetaDataCursor::next();
	if (sqlresult != SQL_SUCCESS)
		return sqlresult;

	ifx_sqlvar_t *pfields = getFields();

	short coltype = *reinterpret_cast<short *>(pfields[ColumnsSQL_coltype].sqldata);

	*reinterpret_cast<INT32 *>(pfields[ColumnsSQL_column_type].sqldata) = ISSERIALTYPE(coltype) ? eSQLAutoIncr : 0;

	UINT16 uLogType, uSubType;
	getLogical(coltype, &uLogType, &uSubType);

	*reinterpret_cast<INT32 *>(pfields[ColumnsSQL_column_datatype].sqldata) = uLogType;

	strcpy(pfields[ColumnsSQL_column_typename].sqldata, rtypname(coltype));

	*reinterpret_cast<INT32 *>(pfields[ColumnsSQL_column_subtype].sqldata) = uSubType;

	short collength = *reinterpret_cast<short *>(pfields[ColumnsSQL_column_length].sqldata);

	if (coltype == SQLDECIMAL)
	{
		*reinterpret_cast<INT32 *>(pfields[ColumnsSQL_column_precision].sqldata) = collength / 0x100;
		*reinterpret_cast<INT32 *>(pfields[ColumnsSQL_column_scale].sqldata) = collength % 0x100;
	}

	*reinterpret_cast<INT32 *>(pfields[ColumnsSQL_column_nullable].sqldata) = coltype & 0x100;

	return SQL_SUCCESS;
}

inline CInformixIndicesCursor::CInformixIndicesCursor ()
{
	m_recno = 0;
	m_colno = 0;
	m_phashTabCol = new _hash_t<_cstr_t, _cstr_t, compare_cstr>(31);
}

inline CInformixIndicesCursor::~CInformixIndicesCursor ()
{
	delete m_phashTabCol;
}

SQLResult CInformixIndicesCursor::initialize (CInformixMetaDataCursor *pColumnsCursor, UINT32 index_type_filter, int IndicesSQL_part_end)
{
	ifx_sqlvar_t *pfields = pColumnsCursor->getFields();

	SQLResult sqlresult;

	_cstr_t strBuffer(20);

	while ((sqlresult = pColumnsCursor->next()) == SQL_SUCCESS)
	{
		sprintf(strBuffer, "%d.%hi",
			*reinterpret_cast<int *>(pfields[ColumnsShortSQL_tabid].sqldata),
			*reinterpret_cast<short *>(pfields[ColumnsShortSQL_colno].sqldata));
		char *pcolname = pfields[ColumnsShortSQL_colname].sqldata;
		trimString(pcolname);
		m_phashTabCol->add(strBuffer)->value = pcolname;
	}

	delete pColumnsCursor;

	m_index_type_filter = index_type_filter;

	m_IndicesSQL_part_end = IndicesSQL_part_end;

	return sqlresult;
}

SQLMETHODIMP CInformixIndicesCursor::next ()
{
	TRACE("CInformixIndicesCursor::next");

	SQLResult sqlresult;

	ifx_sqlvar_t *pfields = getFields();

	if (m_colno == 0 || m_partcol == m_IndicesSQL_part_end)
	{
		sqlresult = CInformixMetaDataCursor::next();
		if (sqlresult != SQL_SUCCESS)
			return sqlresult;

		UINT32 *pindex_type = reinterpret_cast<UINT32 *>(pfields[IndicesSQL_index_type].sqldata);

		if (*pfields[IndicesSQL_idxtype].sqldata == 'U')
		{
			*pindex_type = eSQLUnique;
			if (strcmp(pfields[IndicesSQL_index_name].sqldata, pfields[IndicesSQL_pkey_name].sqldata) == 0)
				*pindex_type += eSQLPrimaryKey;
		}
		else
			*pindex_type = eSQLNonUnique;

		if (m_index_type_filter != 0 && (*pindex_type & m_index_type_filter) == 0)
			return next();

		m_partcol = IndicesSQL_part_begin;

		m_tabid = *reinterpret_cast<int *>(pfields[IndicesSQL_tabid].sqldata);
	}
	else
		m_partcol++;

	m_colno = *reinterpret_cast<short *>(pfields[m_partcol].sqldata);
	if (m_colno == 0)
		return next();

	*reinterpret_cast<INT32 *>(pfields[IndicesSQL_recno].sqldata) = m_recno++;

	if (m_colno < 0)
	{
		*pfields[IndicesSQL_sort_order].sqldata = 'D';
		m_colno = -m_colno;
	}
	else
		*pfields[IndicesSQL_sort_order].sqldata = 'A';

	_cstr_t strBuffer(20);
	sprintf(strBuffer, "%d.%d", m_tabid, m_colno);
	strcpy(pfields[IndicesSQL_column_name].sqldata, m_phashTabCol->get(strBuffer)->value);

	*reinterpret_cast<INT32 *>(pfields[IndicesSQL_column_position].sqldata) = m_partcol - IndicesSQL_part_begin + 1;

	return SQL_SUCCESS;
}

SQLResult CInformixMetaData::getInfo (pCHAR pszSQL, int cParams, ParamDesc* pParams, CInformixMetaDataCursor *pCur, ppSQLCursor ppCur)
{
	if (pCur == 0)
		return DBXERR_NOMEMORY;

	SQLResult sqlresult;

	//
	// prepare statement
	//
	sqlresult = m_Command.prepare(pszSQL, cParams);
	if (sqlresult == SQL_SUCCESS)
	{
		//
		// set parameters
		//
		for (int i = 1; i <= cParams; i++)
		{
			sqlresult = m_Command.setParameter(i, 0, paramIN, pParams->uLogType, pParams->uSubType, 0, 0, pParams->ulLength, pParams->pBuffer, 0);
			if (sqlresult != SQL_SUCCESS) break;
			pParams++;
		}

		if (sqlresult == SQL_SUCCESS)
		{
			//
			// open cursor for tables
			//
			sqlresult = m_Command.checkCurrent();
			if (sqlresult == SQL_SUCCESS)
			{
				sqlresult = pCur->setCommand(&m_Command);
				if (sqlresult == SQL_SUCCESS)
				{
					if (ppCur != 0) *ppCur = pCur;
				}
				else
					delete pCur;
			}
		}
	}

	//
	// unprepare command
	//
	m_Command.unprepare();

	return sqlresult;
}

void CInformixMetaData::setConnection (CInformixConnection *pConnection)
{
	m_Command.setConnection(pConnection);
	m_pConnection = pConnection;
}

SQLMETHODIMP CInformixMetaData::setOption (eSQLMetaDataOption eDOption, INT32 lValue)
{
	TRACE("CInformixMetaData::setOption eDOption=%d lValue=%d\n", eDOption, lValue);

	switch (eDOption)
	{
	case eMetaCatalogName:
		{
			m_strDatabase = reinterpret_cast<char *>(lValue);
		}; break;
	case eMetaSchemaName:
		{
			m_strUser = reinterpret_cast<char *>(lValue);
		}; break;
	case eMetaDatabaseName:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaDatabaseVersion:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaTransactionIsoLevel:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaSupportsTransaction:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaMaxObjectNameLength:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaMaxColumnsInTable:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaMaxColumnsInSelect:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaMaxRowSize:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaMaxSQLLength:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaObjectQuoteChar:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaSQLEscapeChar:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaProcSupportsCursor:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaProcSupportsCursors:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaSupportsTransactions:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	}

	return SQL_SUCCESS;
}

SQLMETHODIMP CInformixMetaData::getOption (eSQLMetaDataOption eDOption, pINT32 plValue, INT16 iMaxLength, pINT16 piLength)
{
	TRACE("CInformixMetaData::getOption eDOption=%d plValue=%p iMaxLength=%hu piLength=%p\n", eDOption, plValue, iMaxLength, piLength);

	INT16 iLength = 0;
	switch (eDOption)
	{
	case eMetaCatalogName:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaSchemaName:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaDatabaseName:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaDatabaseVersion:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaTransactionIsoLevel:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaSupportsTransaction:
		{
			return m_pConnection->getOption(eConnSupportsTransaction, plValue, iMaxLength, piLength);
		}; break;
	case eMetaMaxObjectNameLength:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaMaxColumnsInTable:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaMaxColumnsInSelect:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaMaxRowSize:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaMaxSQLLength:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaObjectQuoteChar:
		{
			// Informix has no quote char for objects
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaSQLEscapeChar:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaProcSupportsCursor:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaProcSupportsCursors:
		{
			return DBXERR_NOTSUPPORTED;
		}; break;
	case eMetaSupportsTransactions:
		{
			return m_pConnection->getOption(eConnMultipleTransaction, plValue, iMaxLength, piLength);
		}; break;
	}
	*piLength = iLength;

	return SQL_SUCCESS;
}

char * checkPattern (char *pszPattern)
{
	if ((pszPattern == 0) || (*pszPattern == '\0'))
		return "%";
	return pszPattern;
}

SQLMETHODIMP CInformixMetaData::getObjectList (eSQLObjectType eObjType, ppSQLCursor ppCur)
{
	TRACE("CInformixMetaData::getObjectList eObjType=%d ppCur=%p\n", eObjType, ppCur);

	return getInfo(const_cast<pCHAR>(rgszObjectSQL[eObjType]), 0, 0, new CInformixMetaDataCursor, ppCur);
}

SQLMETHODIMP CInformixMetaData::getTables (pCHAR pszTableName, UINT32 uTableType, ppSQLCursor ppCur)
{
	TRACE("CInformixMetaData::getTables pszTableName=%s uTableType=%d ppCur=%p\n", pszTableName, uTableType, ppCur);

	pszTableName = checkPattern(pszTableName);

	ParamDesc param = { fldZSTRING, fldstFIXED, strlen(pszTableName) + 1, pszTableName };

	return getInfo(const_cast<pCHAR>(szTablesSQL), 1, &param, new CInformixTablesCursor, ppCur);
}

SQLMETHODIMP CInformixMetaData::getProcedures (pCHAR pszProcName, UINT32 uProcType, ppSQLCursor ppCur)
{
	TRACE("CInformixMetaData::getProcedures pszProcName=%s uProcType=%d ppCur=%p\n", pszProcName, uProcType, ppCur);

	pszProcName = checkPattern(pszProcName);

	ParamDesc param = { fldZSTRING, fldstFIXED, strlen(pszProcName) + 1, pszProcName };

	return getInfo(const_cast<pCHAR>(szProceduresSQL), 1, &param, new CInformixMetaDataCursor, ppCur);
}

SQLMETHODIMP CInformixMetaData::getColumns (pCHAR pszTableName, pCHAR pszColumnName, UINT32 uColType, ppSQLCursor ppCur)
{
	TRACE("CInformixMetaData::getColumns pszTableName=%s pszColumnName=%s uColType=%d ppCur=%p\n", pszTableName, pszColumnName, uColType, ppCur);

	pszTableName = checkPattern(pszTableName);
	pszColumnName = checkPattern(pszColumnName);

	ParamDesc params[2] =
	{
		{ fldZSTRING, fldstFIXED, strlen(pszTableName) + 1, pszTableName },
		{ fldZSTRING, fldstFIXED, strlen(pszColumnName) + 1, pszColumnName }
	};

	SQLResult sqlresult;

	CInformixColumnsCursor *pCur = new CInformixColumnsCursor;

	sqlresult = getInfo(const_cast<pCHAR>(szColumnsSQL), 2, params, pCur, ppCur);

	//
	// adjust buffer for type name
	//
	if (sqlresult == SQL_SUCCESS)
	{
		ifx_sqlvar_t *pvar = &pCur->getFields()[ColumnsSQL_column_typename];
		pvar->sqllen = 128;
		pvar->sqldata = static_cast<char *>(realloc(pvar->sqldata, 129));
	}

	return sqlresult;
}

SQLMETHODIMP CInformixMetaData::getProcedureParams(pCHAR pszProcName, pCHAR pszParamName, ppSQLCursor ppCur)
{
	TRACE("CInformixMetaData::getProcedureParams pszProcName=%s pszParamName=%s ppCur=%p\n", pszProcName, pszParamName, ppCur);

	return DBXERR_NOTSUPPORTED;
}

SQLMETHODIMP CInformixMetaData::getIndices (pCHAR pszTableName, UINT32 uIndexType, ppSQLCursor ppCur)
{
	TRACE("CInformixMetaData::getIndices pszTableName=%s uIndexType=%d ppCur=%p\n", pszTableName, uIndexType, ppCur);

	SQLResult sqlresult;

	pszTableName = checkPattern(pszTableName);
	ParamDesc param = { fldZSTRING, fldstFIXED, strlen(pszTableName) + 1, pszTableName };

	//
	// cursor for columns short information
	//
	CInformixMetaDataCursor *pColumnsCur = new CInformixMetaDataCursor;
	sqlresult = getInfo(const_cast<pCHAR>(szColumnsShortSQL), 1, &param, pColumnsCur, 0);
	if (sqlresult != SQL_SUCCESS)
		return sqlresult;

	//
	// cursor for specified tables indecies
	//
	CInformixIndicesCursor *pIndicesCur = new CInformixIndicesCursor;
	sqlresult = pIndicesCur->initialize(pColumnsCur, uIndexType, IndicesSQL_part_end[m_pConnection->isNotInformixSE()]);
	if (sqlresult != DBXERR_EOF)
		return sqlresult;

	sqlresult = getInfo(const_cast<pCHAR>(rgszIndicesSQL[m_pConnection->isNotInformixSE()]), 1, &param, pIndicesCur, ppCur);

	//
	// adjust buffer for column name
	//
	if (sqlresult == SQL_SUCCESS)
	{
		ifx_sqlvar_t *pvar = &pIndicesCur->getFields()[IndicesSQL_column_name];
		pvar->sqllen = 128;
		pvar->sqldata = static_cast<char *>(realloc(pvar->sqldata, 129));
	}

	return sqlresult;
}

SQLMETHODIMP CInformixMetaData::getErrorMessage (pBYTE pszError)
{
	return m_Command.getErrorMessage(pszError);
}

SQLMETHODIMP CInformixMetaData::getErrorMessageLen (pUINT16 puErrorLen)
{
	return m_Command.getErrorMessageLen(puErrorLen);
}


/////////////////////////////////////////////////////////////////////////////
// Miscellaneous routines

void trimString (char *lpsz)
{
	char *lpzero = lpsz;

	while (*lpsz != '\0')
		if (*lpsz++ != ' ') lpzero = lpsz;

	*lpzero = '\0';
}

void free_hostvar (ifx_sqlvar_t *pvar, bool local_lob)
{
	if (pvar->sqldata != 0)
	{
		//
		// free lob buffer
		//
		if ISBLOBTYPE(pvar->sqltype)
#ifdef WIN32
		{
			void *pbuffer = reinterpret_cast<loc_t *>(pvar->sqldata)->loc_buffer;
			if (local_lob)
				free(pbuffer);
			else
				SqlFreeMem(pbuffer, STRING_FREE);
		}
#else
			free(reinterpret_cast<loc_t *>(pvar->sqldata)->loc_buffer);
#endif

		//
		// free data buffer
		//
		free(pvar->sqldata);
	}

	//
	// free indicator
	//
	free(pvar->sqlind);
}

void clear_hostvar (ifx_sqlvar_t *pvar, bool local_lob)
{
	free_hostvar(pvar, local_lob);
	memset(pvar, 0, sizeof(ifx_sqlvar_t));
}

void free_sqlda (ifx_sqlda_t *psqlda, bool local_lob)
{
	if (psqlda != 0)
	{
		//
		// access to host variable structure
		//
		ifx_sqlvar_t *pvar = psqlda->sqlvar;

		//
		// free host variables buffers
		//
		for (int i = 0; i < psqlda->sqld; i++)
			free_hostvar(pvar++, local_lob);

		//
		// free host variables array
		//
		free(psqlda->sqlvar);

		//
		// free descriptor
		//
		free(psqlda);
	}
}


#ifdef WIN32

/////////////////////////////////////////////////////////////////////////////
// DLL entry point

#include <windows.h>

BOOL APIENTRY DllMain(HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
	switch (ul_reason_for_call)
	{
		case DLL_PROCESS_ATTACH:
		case DLL_THREAD_ATTACH:
		case DLL_THREAD_DETACH:
		case DLL_PROCESS_DETACH: break;
	}
	return TRUE;
}

#endif // WIN32
